{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "initial_id",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:39.580119Z",
     "start_time": "2025-01-21T14:09:39.573580Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=10, micro=14, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cu124\n",
      "cuda:0\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "1dad802b6500ab83",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.656263Z",
     "start_time": "2025-01-21T14:09:39.699901Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(PosixPath('competitions/cifar-10/train/1.png'), 'frog'),\n",
      " (PosixPath('competitions/cifar-10/train/2.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/3.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/4.png'), 'deer'),\n",
      " (PosixPath('competitions/cifar-10/train/5.png'), 'automobile')]\n",
      "[(PosixPath('competitions/cifar-10/test/1.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/2.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/3.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/4.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/5.png'), 'cat')]\n",
      "50000 300000\n"
     ]
    }
   ],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "# 目的：通过读取csv文件，得到图片的类别，并将图片路径和类别保存到DataFrame中。\n",
    "\n",
    "DATA_DIR1 = Path(\"./\")\n",
    "DATA_DIR2=Path(\"./competitions/cifar-10/\")\n",
    "\n",
    "train_labels_file = DATA_DIR1 / \"trainLabels.csv\"\n",
    "test_csv_file = DATA_DIR1 / \"sampleSubmission.csv\"  # 测试集模板csv文件\n",
    "train_folder = DATA_DIR2 / \"train/\"\n",
    "test_folder = DATA_DIR2 / \"test/\"\n",
    "\n",
    "# 所有的类别\n",
    "class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']\n",
    "\n",
    "\n",
    "# filepath:csv文件路径，folder:图片所在文件夹\n",
    "def parse_csv_file(filepath, folder):\n",
    "    result = []\n",
    "    # 读取所有行\n",
    "    # with 语句：用于管理文件的上下文。在 with 块结束时，文件会自动关闭，无需手动调用 f.close()。\n",
    "    with open(filepath, 'r') as f:\n",
    "        # f.readlines()：读取文件的所有行，返回一个列表，每个元素是一行内容。\n",
    "        # 第一行不需要，因为第一行是标题\n",
    "        lines = f.readlines()[1:]\n",
    "    for line in lines:\n",
    "        # strip('\\n')：去除行末的换行符（\\n）。\n",
    "        # split(',')：按','分割字符串，返回一个列表。\n",
    "        image_id, label_str = line.strip('\\n').split(',')\n",
    "        # 得到图片的路径\n",
    "        image_full_path = folder / f\"{image_id}.png\"\n",
    "        # 得到对应图片的路径和分类\n",
    "        result.append((image_full_path, label_str))\n",
    "    return result\n",
    "\n",
    "\n",
    "# 得到训练集和测试集的图片路径和分类\n",
    "train_labels_info = parse_csv_file(train_labels_file, train_folder)\n",
    "test_csv_info = parse_csv_file(test_csv_file, test_folder)\n",
    "\n",
    "#打印\n",
    "import pprint\n",
    "\n",
    "pprint.pprint(train_labels_info[0:5])\n",
    "pprint.pprint(test_csv_info[0:5])\n",
    "print(len(train_labels_info), len(test_csv_info))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "8cd5063c39c678cf",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.732204Z",
     "start_time": "2025-01-21T14:09:41.658275Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                            filepath       class\n",
      "0  competitions/cifar-10/train/1.png        frog\n",
      "1  competitions/cifar-10/train/2.png       truck\n",
      "2  competitions/cifar-10/train/3.png       truck\n",
      "3  competitions/cifar-10/train/4.png        deer\n",
      "4  competitions/cifar-10/train/5.png  automobile\n",
      "                                filepath       class\n",
      "0  competitions/cifar-10/train/45001.png       horse\n",
      "1  competitions/cifar-10/train/45002.png  automobile\n",
      "2  competitions/cifar-10/train/45003.png        deer\n",
      "3  competitions/cifar-10/train/45004.png  automobile\n",
      "4  competitions/cifar-10/train/45005.png    airplane\n",
      "                           filepath class\n",
      "0  competitions/cifar-10/test/1.png   cat\n",
      "1  competitions/cifar-10/test/2.png   cat\n",
      "2  competitions/cifar-10/test/3.png   cat\n",
      "3  competitions/cifar-10/test/4.png   cat\n",
      "4  competitions/cifar-10/test/5.png   cat\n"
     ]
    }
   ],
   "source": [
    "# 取前45000张图片作为训练集\n",
    "train_df = pd.DataFrame(train_labels_info[0:45000])\n",
    "# 取后5000张图片作为验证集\n",
    "valid_df = pd.DataFrame(train_labels_info[45000:])\n",
    "# 测试集\n",
    "test_df = pd.DataFrame(test_csv_info)\n",
    "\n",
    "# 为 Pandas DataFrame 的列重新命名\n",
    "train_df.columns = ['filepath', 'class']\n",
    "valid_df.columns = ['filepath', 'class']\n",
    "test_df.columns = ['filepath', 'class']\n",
    "\n",
    "print(train_df.head())\n",
    "print(valid_df.head())\n",
    "print(test_df.head())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "af9444393c2debca",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.740688Z",
     "start_time": "2025-01-21T14:09:41.732204Z"
    }
   },
   "outputs": [],
   "source": [
    "from PIL import Image\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "from torchvision import transforms\n",
    "\n",
    "\n",
    "class Cifar10Dataset(Dataset):\n",
    "    df_map = {\n",
    "        \"train\": train_df,\n",
    "        \"eval\": valid_df,\n",
    "        \"test\": test_df\n",
    "    }\n",
    "    # enumerate(class_names)：遍历 class_names，返回索引和类别名称的元组\n",
    "    # 将类别名称映射为索引，通常用于将标签转换为模型可以处理的数值。\n",
    "    label_to_idx = {\n",
    "        label: idx for idx, label in enumerate(class_names)\n",
    "    }\n",
    "    # 将索引映射为类别名称，通常用于将模型的输出（索引）转换回可读的类别名称\n",
    "    idx_to_label = {\n",
    "        idx: label for idx, label in enumerate(class_names)\n",
    "    }\n",
    "\n",
    "    def __init__(self, mode, transform=None):\n",
    "        # 获取对应模式的df，不同字符串对应不同模式\n",
    "        self.df = self.df_map.get(mode, None)\n",
    "        if self.df is None:\n",
    "            raise ValueError(f\"Invalid mode: {mode}\")\n",
    "        self.transform = transform\n",
    "\n",
    "    def __getitem__(self, index):\n",
    "        # 获取图片路径和标签\n",
    "        img_path, label = self.df.iloc[index]\n",
    "        # 打开一张图片并将其转换为 RGB 格式\n",
    "        img = Image.open(img_path).convert('RGB')  # 确保图片具有三个通道\n",
    "        # 应用数据增强\n",
    "        img = self.transform(img)\n",
    "        # label 转换为 idx\n",
    "        label = self.label_to_idx[label]\n",
    "        return img, label\n",
    "\n",
    "    def __len__(self):\n",
    "        # 返回df的行数,样本数\n",
    "        return self.df.shape[0]\n",
    "\n",
    "\n",
    "IMAGE_SIZE = 32\n",
    "mean, std = [0.4914, 0.4822, 0.4465], [0.247, 0.243, 0.261]\n",
    "\n",
    "#数据增强\n",
    "# ToTensor还将图像的维度从[height, width, channels]转换为[channels, height, width]。\n",
    "transforms_train = transforms.Compose([\n",
    "    transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),  # 缩放\n",
    "    transforms.RandomRotation(40),  # 随机旋转\n",
    "    transforms.RandomHorizontalFlip(),  # 随机水平翻转\n",
    "    transforms.ToTensor(),  # 转换为Tensor\n",
    "    transforms.Normalize(mean, std)  # 标准化\n",
    "])\n",
    "\n",
    "transforms_eval = transforms.Compose([\n",
    "    transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n",
    "    transforms.ToTensor(),\n",
    "    transforms.Normalize(mean, std)\n",
    "])\n",
    "\n",
    "train_ds = Cifar10Dataset(mode=\"train\", transform=transforms_train)\n",
    "eval_ds = Cifar10Dataset(mode=\"eval\", transform=transforms_eval)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "d17ef4a56f0b6ae1",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.748174Z",
     "start_time": "2025-01-21T14:09:41.742701Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([3, 32, 32])"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_ds[0][0].shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "161282a4e612afcd",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.753134Z",
     "start_time": "2025-01-21T14:09:41.749187Z"
    }
   },
   "outputs": [],
   "source": [
    "batch_size = 64\n",
    "\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size,\n",
    "                          shuffle=True)\n",
    "eval_loader = DataLoader(eval_ds, batch_size=batch_size,\n",
    "                         shuffle=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20b5b31416450980",
   "metadata": {},
   "source": [
    "## 模型定义"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "b73daeeb83b4b527",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.760182Z",
     "start_time": "2025-01-21T14:09:41.754138Z"
    }
   },
   "outputs": [],
   "source": [
    "class Resdiual(nn.Module):\n",
    "    \"\"\"\n",
    "    浅层的残差块，无bottleneck（性能限制\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, input_channels, output_channels,\n",
    "                 use_1x1conv=False, stride=1):\n",
    "        \"\"\"\n",
    "        残差块\n",
    "        params filters: 过滤器数目，决定输出通道\n",
    "        params use_1x1conv: 是否使用 1x1 卷积，此时 stride=2，进行降采样\n",
    "        params strides: 步长，默认为1，当降采样的时候设置为2\n",
    "        \"\"\"\n",
    "        super().__init__()\n",
    "        # [1+(n+2-3)]//1=n \n",
    "        # [1+(n+2-3)]//2=n//2\n",
    "        # 即该方法可能会降采样\n",
    "        self.conv1 = nn.Conv2d(\n",
    "            in_channels=input_channels,\n",
    "            out_channels=output_channels,\n",
    "            kernel_size=3,\n",
    "            stride=stride,\n",
    "            padding=1\n",
    "        )\n",
    "        # [1+(n+2-3)]//1=n \n",
    "        self.conv2 = nn.Conv2d(\n",
    "            in_channels=output_channels,\n",
    "            out_channels=output_channels,\n",
    "            kernel_size=3,\n",
    "            stride=1,\n",
    "            padding=1\n",
    "        )\n",
    "        if use_1x1conv:\n",
    "            # skip connection 的 1x1 卷积，用于改变通道数和降采样，使得最终可以做残差连接\n",
    "            # [1+n-1]//s=n//s\n",
    "            self.conv_sc = nn.Conv2d(\n",
    "                in_channels=input_channels,\n",
    "                out_channels=output_channels,\n",
    "                kernel_size=1,\n",
    "                stride=stride\n",
    "            )\n",
    "        else:\n",
    "            self.conv_sc = None\n",
    "\n",
    "        self.bn1 = nn.BatchNorm2d(output_channels, eps=1e-5, momentum=0.9)\n",
    "        self.bn2 = nn.BatchNorm2d(output_channels, eps=1e-5, momentum=0.9)\n",
    "\n",
    "    def forward(self, inputs):\n",
    "        flow = F.relu(self.bn1(self.conv1(inputs)))  # 卷积->BN->ReLU\n",
    "        flow = self.bn2(self.conv2(flow))  # 卷积->BN\n",
    "        if self.conv_sc:\n",
    "            inputs = self.conv_sc(inputs)  # 1x1卷积，改变通道数和降采样\n",
    "        # 残差连接->ReLU，必须保证flow和inputs的shape相同\n",
    "        return F.relu(flow + inputs)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "fc16467b5391fd55",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.766476Z",
     "start_time": "2025-01-21T14:09:41.761185Z"
    }
   },
   "outputs": [],
   "source": [
    "class ResdiualBlock(nn.Module):\n",
    "    \"\"\"\n",
    "    若干个 Resdiual 模块堆叠在一起，\n",
    "    通常在第一个模块给 skip connection 使用 1x1conv with stride=2\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, input_channels, output_channels, num, is_first=False):\n",
    "        \"\"\"\n",
    "        params filters: 过滤器数目\n",
    "        params num: 堆叠几个 Resdiual 模块\n",
    "        params is_first: 是不是第一个block。 最上面一层 Resdiual 的 stride=1,is_first=False,图像尺寸减半，False图像尺寸不变\n",
    "        \"\"\"\n",
    "        super().__init__()\n",
    "        self.model = nn.Sequential()  # 用于存放 Resdiual 模块\n",
    "        # append() 等价于 add_module()\n",
    "        self.model.append(Resdiual(\n",
    "            input_channels=input_channels,\n",
    "            output_channels=output_channels,\n",
    "            use_1x1conv=not is_first,\n",
    "            stride=1 if is_first else 2\n",
    "        ))  # 第一个 Resdiual 模块，负责通道翻倍,图像的尺寸减半\n",
    "        for _ in range(1, num):\n",
    "            # 堆叠 num 个 Resdiual 模块\n",
    "            self.model.append(Resdiual(\n",
    "                input_channels=output_channels,\n",
    "                output_channels=output_channels,\n",
    "                use_1x1conv=False,\n",
    "                stride=1\n",
    "            ))\n",
    "\n",
    "    def forward(self, inputs):\n",
    "        return self.model(inputs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "8128761d5d6c0e1d",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.773688Z",
     "start_time": "2025-01-21T14:09:41.767479Z"
    }
   },
   "outputs": [],
   "source": [
    "class ResNetCifar10(nn.Module):\n",
    "    def __init__(self, n=3, num_classes=10):\n",
    "        \"\"\"\n",
    "        params units: 预测类别的数目\n",
    "        \"\"\"\n",
    "        super().__init__()\n",
    "        self.model = nn.Sequential(\n",
    "            # conv1\n",
    "            nn.Conv2d(in_channels=3, out_channels=16,\n",
    "                      kernel_size=3, stride=1),\n",
    "            nn.BatchNorm2d(16, momentum=0.9, eps=1e-5),\n",
    "            nn.ReLU(),\n",
    "            # conv2_x\n",
    "            ResdiualBlock(input_channels=16, output_channels=16,\n",
    "                          num=2 * n, is_first=True),\n",
    "            # conv3_x\n",
    "            ResdiualBlock(input_channels=16, output_channels=32,\n",
    "                          num=2 * n),\n",
    "            # conv4_x\n",
    "            ResdiualBlock(input_channels=32, output_channels=64,\n",
    "                          num=2 * n),\n",
    "            # 全局平均池化层\n",
    "            #无论输入图片大小，输出都是1x1，把width和height压缩为1\n",
    "            nn.AdaptiveAvgPool2d((1, 1)),\n",
    "            # 全连接层\n",
    "            nn.Flatten(),  # 64*1*1 -> 64\n",
    "            # 输出层\n",
    "            nn.Linear(in_features=64, out_features=num_classes)\n",
    "        )\n",
    "        self.init_weights()\n",
    "\n",
    "    def init_weights(self):\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, (nn.Linear, nn.Conv2d)):\n",
    "                # Kaiming 初始化（也称为 He 初始化）是为深度神经网络设计的一种初始化方法，特别适用于 ReLU 激活函数。\n",
    "                nn.init.kaiming_uniform_(m.weight)\n",
    "                if m.bias is not None:\n",
    "                    nn.init.zeros_(m.bias)\n",
    "    \n",
    "    def forward(self, inputs):\n",
    "        return self.model(inputs)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "bb519b4756d62090",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.777720Z",
     "start_time": "2025-01-21T14:09:41.774691Z"
    }
   },
   "outputs": [],
   "source": [
    "# for key, value in ResNetCifar10(len(class_names)).named_parameters():\n",
    "#     print(f\"{key:^40}paramerters num: {np.prod(value.shape)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "b294d25e7f6287d0",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.825513Z",
     "start_time": "2025-01-21T14:09:41.779726Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total trainable parameters: 1929546\n"
     ]
    }
   ],
   "source": [
    "total_params = sum(p.numel() for p in ResNetCifar10(len(class_names)).parameters() if p.requires_grad)\n",
    "print(f\"Total trainable parameters: {total_params}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "76ca49d0dca74376",
   "metadata": {},
   "source": [
    "## 模型训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "632d31ce33563b0c",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.909287Z",
     "start_time": "2025-01-21T14:09:41.826518Z"
    }
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "\n",
    "@torch.no_grad()  # 装饰器，禁止梯度计算\n",
    "def evaluate(model, data_loader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in data_loader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "\n",
    "        # 前向传播\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)  # 验证集损失\n",
    "        # tensor.item() 获取tensor的数值，loss是只有一个元素的tensor\n",
    "        loss_list.append(loss.item())\n",
    "\n",
    "        # 预测\n",
    "        preds = logits.argmax(axis=-1)  # 预测类别\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())  # tensor转numpy，再转list\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "\n",
    "    acc = accuracy_score(label_list, pred_list)  # 计算准确率\n",
    "    return np.mean(loss_list), acc  # # 返回验证集平均损失和准确率"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "28c2ba49f4ff546",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.915505Z",
     "start_time": "2025-01-21T14:09:41.910290Z"
    }
   },
   "outputs": [],
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=500, save_best_only=True):\n",
    "        self.save_dir = save_dir  # 保存路径\n",
    "        self.save_step = save_step  # 保存步数\n",
    "        self.save_best_only = save_best_only  # 是否只保存最好的模型\n",
    "        self.best_metric = -1  # 最好的指标，指标不可能为负数，所以初始化为-1\n",
    "        # 创建保存路径\n",
    "        if not os.path.exists(self.save_dir):  # 如果不存在保存路径，则创建\n",
    "            os.makedirs(self.save_dir)\n",
    "\n",
    "    # 对象被调用时：当你将对象像函数一样调用时，Python 会自动调用 __call__ 方法。\n",
    "    # state_dict() 返回模型参数的字典，包括模型参数和优化器参数\n",
    "    # metric 是指标，可以是验证集的准确率，也可以是其他指标\n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return  # 不是保存步数，则直接返回\n",
    "\n",
    "        if self.save_best_only:\n",
    "            assert metric is not None  # 必须传入metric\n",
    "            if metric >= self.best_metric:  # 如果当前指标大于最好的指标\n",
    "                # save checkpoint\n",
    "                # 保存最好的模型，覆盖之前的模型，不保存step，只保存state_dict，即模型参数，不保存优化器参数\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"08_resnet.ckpt\"))\n",
    "                self.best_metric = metric  # 更新最好的指标\n",
    "        else:\n",
    "            # 保存模型\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "            # 保存每个step的模型，不覆盖之前的模型，保存step，保存state_dict，即模型参数，不保存优化器参数\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "cd4d61586ad24eb5",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.921575Z",
     "start_time": "2025-01-21T14:09:41.916508Z"
    }
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        self.patience = patience  # 多少个step没有提升就停止训练\n",
    "        self.min_delta = min_delta  # 最小的提升幅度\n",
    "        self.best_metric = -1  # 记录的最好的指标\n",
    "        self.counter = 0  # 计数器，记录连续多少个step没有提升\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:  # 如果指标提升了\n",
    "            self.best_metric = metric  # 更新最好的指标\n",
    "            self.counter = 0  # 计数器清零\n",
    "        else:\n",
    "            self.counter += 1  # 计数器加一\n",
    "\n",
    "    @property  # 使用@property装饰器，使得 对象.early_stop可以调用，不需要()\n",
    "    def early_stop(self):\n",
    "        # 如果计数器大于等于patience，则返回True，停止训练\n",
    "        return self.counter >= self.patience"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "725cf8a521379801",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.930997Z",
     "start_time": "2025-01-21T14:09:41.922578Z"
    }
   },
   "outputs": [],
   "source": [
    "def training(model,\n",
    "             train_loader,\n",
    "             val_loader,\n",
    "             epoch,\n",
    "             loss_fct,\n",
    "             optimizer,\n",
    "             save_ckpt_callback=None,\n",
    "             early_stop_callback=None,\n",
    "             eval_step=500,\n",
    "             ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 0  # 全局步数\n",
    "    model.train()  # 训练模式\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "\n",
    "                # 前向传播\n",
    "                logits = model(datas)\n",
    "                loss = loss_fct(logits, labels)  # 训练集损失\n",
    "                preds = logits.argmax(axis=-1)  # 预测类别\n",
    "\n",
    "                # 反向传播\n",
    "                optimizer.zero_grad()  # 梯度清零\n",
    "                loss.backward()  # 反向传播\n",
    "                optimizer.step()  # 优化器更新参数\n",
    "\n",
    "                # 计算准确率\n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())\n",
    "                loss = loss.cpu().item()\n",
    "\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss,\n",
    "                    \"acc\": acc,\n",
    "                    \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # 评估\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()  # 评估模式\n",
    "                    # 验证集损失和准确率\n",
    "                    val_loss, val_acc = evaluate(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss,\n",
    "                        \"acc\": val_acc,\n",
    "                        \"step\": global_step\n",
    "                    })\n",
    "                    model.train()  # 训练模式\n",
    "\n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        # model.state_dict() 返回模型参数的字典，包括模型参数和优化器参数\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), val_acc)\n",
    "                        # 保存最好的模型，覆盖之前的模型，保存step，保存state_dict,通过metric判断是否保存最好的模型\n",
    "\n",
    "                    # 3. 早停 early stopping\n",
    "                    if early_stop_callback is not None:\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        early_stop_callback(val_acc)\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict  # 早停，返回记录字典 record_dict\n",
    "\n",
    "                # 更新进度条和全局步数\n",
    "                pbar.update(1)  # 更新进度条\n",
    "                global_step += 1  # 全局步数加一\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    return record_dict  # 训练结束，返回记录字典 record_dict\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "22bf1e82c9715088",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.979444Z",
     "start_time": "2025-01-21T14:09:41.931999Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "epoch = 100\n",
    "\n",
    "model = ResNetCifar10(len(class_names))  # 定义模型\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "\n",
    "# 2. 定义优化器 采用 SGD\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\n",
    "\n",
    "# 3.save model checkpoint\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(save_dir=\"checkpoints\", save_step=len(train_loader), save_best_only=True)\n",
    "\n",
    "# 4. early stopping\n",
    "early_stop_callback = EarlyStopCallback(patience=5, min_delta=0.01)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "5335f62fe5e4977f",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:10:19.118493Z",
     "start_time": "2025-01-21T14:09:41.980446Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 32%|███▏      | 22528/70400 [28:05<59:42, 13.36it/s, epoch=31]  "
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 32 / global_step 22528\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "model = model.to(device)  # 将模型移到GPU上\n",
    "\n",
    "# 训练过程\n",
    "record_dict = training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    eval_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "1a790e5f4635d5e8",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:10:19.120498Z",
     "start_time": "2025-01-21T14:10:19.119497Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "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xZlnnslPf/pTli1bxqBBg8jPz0dRFH74wx+yZMkSRo0axQ033MCvf/1rLBYLH330EZdcconfnjJxammYaSmtsodwJOHlaHkdNY76NzAalopFkqCCltNPP50nnniCPXv2MGTIEL7++ms+++wzli1b5vccu92O3V7/g6PXtjqdTpxOZ1CDNaD9EnWpBH3uqUCfE5mb5mRu/NPnxOXSllP1eDydWvrUHRYvXszChQsZMWIEdXV1rFq1CqDZc3n44Yf5xS9+wemnn056ejq///3vqays9D1vnf51w+Vlm85Jw/vet28fpaWlvq9VVW10n/fffz9ut5urrrqKqqoqJk6cyLvvvktycjIejwez2cySJUs4ePAgsbGxTJ8+neeffx6Px0N8fDwPPvgg33//PUajkUmTJvmWgG3p/0kft9Pp9K26pJOf/1PLLbfcwvz5832vC30Z7aaWLVvGNddc43td3Hrrrb6/1R2xb9++RpmUVatW8ctf/pLx48eTm5vL/fffzy233NLonNdee41bbrmFyy+/nJqaGgYNGsQDDzwAwJAhQ1i7di1/+MMfmDx5MrGxsUyZMsW3GIA4tamqSmF54/IwodFLwzITrZRU2Tl4vIY6h5tYS8d75LqToga66DvaH8M//OEPPPjggxiNRtxuN/fddx9Llizxe85dd93F3Xff3ez2559/nri4uKAGu6dC4bGdRrJjVZaMlSUOhehMJpOJ7OxscnNzsVgsoR6OaCeHw8Hhw4cpKirC5XI1+l5tbS1XXHEFFRUVJCUlhWiE4aeyspLk5OQW58Vms3HgwAH69+9PTExMi+frzeZJSUkR0dPSnUI5N4H834WS0+lkzZo1zJkzp1l/kAh+fipqnYy5Z63v6+vO6M8ffzSilTMi0+ETtTz8fj65ziPcfPn5Ac3NPz78nofX7mHe2J588n0ZJ2ocvLVoGqN7p3T9gAPQ2u/ghoLKtLz88ss899xzPP/884wcOZLt27fzm9/8hp49ezJ//vwWz1myZAmLFy9uNLDc3FxmzZoV9B/NrEMneWznZtwqzJw5U17kTTidTvLy8mRuWiBz458+N6effjqFhYUkJCSE5R/4UFBVlaqqKhITEyNm6UybzUZsbCxnnnlms//Hznj3XAghwlFhg34WgJIoLA87WeNg/pOb2F9Ww8BEAzcHeJ6eaRmanURxpZ2N+4+T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0GAFMe/mk5Eg4jIt9eVh0tMihNBceeWVvPbaa9jt2h+o5557jp/97GcYDAaqq6u55ZZbGD58OCkpKSQkJLBr165W31FeunQp//nPf/x+f9euXUybNq3RbdOmTWPXLm0TsEsuuYS6ujoGDBjAddddxxtvvIHLpa1sM3PmTPr27cuAAQO46qqreO6556itjZyaYhE5uvp14Xa7uffeexk1ahRpaWkkJCTw/vvv++5j165d2O12zjrrrBbvb/v27YwbN84XsAjRXs0yLd59Wo7XOCKuMuegN2jp30o/i26sd5PJbS0se3y0vI4quwuTQWmxzMy3gphkWrqOxRu0yOphQnQDc5z2zm6oHjtAc+fORVVV3nnnHSZNmsSnn37K3/72NwBuueUW8vLyePjhhxk0aBCxsbFcfPHFOByOrho5ubm57N69mw8++IC8vDxuuOEGHnroIT7++GMSExPZunUr69evZ+3atdxxxx3cddddbN68WZZVjhRNXhcej4fKqiqSEhMxGLr4vcAwel089NBDPPLIIyxfvpxRo0YRHx/Pb37zG999xMbGtnp+W98XIlAN92gBSIuzYDIouDwqZdV2X4N+JNgfRNAyxlsetrekmopaJ8lx9SuE6WVfAzMSfK0VDemZloNlNdicbmLMLa9SFk4iLmiRRnwhupGiBFSKEmoxMTH85Cc/4bnnnmPv3r0MHTqU8ePHA7BhwwYWLFjARRddBGh19AcPHuzQ4w0fPpwNGzYwf/58320bNmxgxIgRvq9jY2OZO3cuc+fO5cYbb2TYsGF8++23jB8/HpPJxIwZM5gxYwZ33nknKSkpfPjhh/zkJz/p0LhEN2n6uvB4wOzWbuvqoCUIXf262LBhAxdeeCE///nPAS1427Nnj+91MHjwYGJjY/n44499PV0NjR49mn//+9+cOHFCsi2iQ3yN+N6gxWBQSE+wUlRpo6QysoIWvTystZXDdD3iLfTrEcfB47VsP1LOWUMyfN/z14Svy0y0khJnprzWyd6Sak7rldwJo+9aERe0WHw9LVIeJoSod+WVV3LBBRewY8cO30UUaBdOr7/+OnPnzkVRFG6//fY2G32XLFnC0aNHWb16dYvf/93vfsell17KuHHjmDFjBv/73/94/fXXfasvrV69GrfbzZQpU4iLi+PZZ58lNjaWvn378vbbb7N//37OPPNMUlNT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",
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def plot_record_curves(record_dict, sample_step=500):\n",
    "    # .set_index(\"step\") 将 step 列设置为 DataFrame 的索引\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    last_step = train_df.index[-1]  # 最后一步的步数\n",
    "\n",
    "    # print(train_df)\n",
    "    # print(val_df)\n",
    "\n",
    "    # 画图 \n",
    "    fig_num = len(train_df.columns)  # 画两张图,分别是损失和准确率\n",
    "\n",
    "    # plt.subplots：用于创建一个包含多个子图的图形窗口。\n",
    "    # 1：表示子图的行数为 1。\n",
    "    # fig_num：表示子图的列数，即子图的数量。\n",
    "    # figsize=(5 * fig_num, 5)：设置整个图形窗口的大小，宽度为 5 * fig_num，高度为 5。\n",
    "    # fig：返回的图形对象（Figure），用于操作整个图形窗口。\n",
    "    # axs：返回的子图对象（Axes 或 Axes 数组），用于操作每个子图。\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        # train_df.index 是 x 轴数据（通常是 step）。\n",
    "        # train_df[item] 是 y 轴数据（当前指标的值）。\n",
    "        axs[idx].plot(train_df.index, train_df[item], label=\"train:\" + item)\n",
    "        # val_df.index 是 x 轴数据。\n",
    "        # val_df[item] 是 y 轴数据。\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=\"val:\" + item)\n",
    "        axs[idx].grid()  # 显示网格\n",
    "        axs[idx].legend()  # 显示图例\n",
    "        axs[idx].set_xticks(range(0, train_df.index[-1] + 1, 5000))  # 设置x轴刻度\n",
    "        axs[idx].set_xticklabels(map(lambda x: f\"{x // 1000}k\", range(0, last_step + 1, 5000)))  # 设置x轴标签\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "plot_record_curves(record_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d9729d15a0d01e8",
   "metadata": {},
   "source": [
    "## 评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "3195e10a60c29806",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:10:19.121498Z",
     "start_time": "2025-01-21T14:10:19.121498Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Test loss: 0.8155, Test acc: 0.7164\n"
     ]
    }
   ],
   "source": [
    "# 加载最好的模型\n",
    "# torch.load：加载保存的模型权重或整个模型。\n",
    "# \"checkpoints/best.ckpt\"：模型权重文件路径。\n",
    "# weights_only=True：仅加载模型的权重，而不是整个模型（包括结构和参数）。这是 PyTorch 2.1 引入的新特性，用于增强安全性。\n",
    "# map_location=device：将模型加载到当前设备（GPU或CPU）。\n",
    "model.load_state_dict(torch.load(\"checkpoints/08_resnet.ckpt\", weights_only=True, map_location=device))  # 加载最好的模型\n",
    "\n",
    "model.eval()  # 评估模式\n",
    "loss, acc = evaluate(model, eval_loader, loss_fct)\n",
    "print(f\"Test loss: {loss:.4f}, Test acc: {acc:.4f}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
